package com.multiplesource.config;

import java.io.IOException;
import java.sql.SQLException;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.AnnotationScopeMetadataResolver;
import org.springframework.context.annotation.ScopeMetadata;
import org.springframework.context.annotation.ScopeMetadataResolver;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.util.StringUtils;

/**
 * 根据数据配置，动态创建bean交给spring管理
 * 并给定扫描对于的数据源的mapper
 * 
 * @author zhuzhoutong
 *
 */
public class MultiDataSourceProcessor implements BeanDefinitionRegistryPostProcessor{
	
	/**
	 * 数据源bean的前缀
	 * 数据源的bean名称就是dataSource_+(spring.datasource.multi的name属性值)
	 */
	public static final String DATASOURCPREFIX = "dataSource_";
	
	public static final String TRANSACTIONPREFIX = "transAction_";
	
	public static final String SQLSESSIONFACTORYPREFIX = "sqlSessionFactory_";
	
	public MultiDataSourceProperties datasourceproperties;
	
    private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
    private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();

    public MultiDataSourceProcessor(MultiDataSourceProperties datasourceproperties) {
    	this.datasourceproperties = datasourceproperties;
	}
    
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 这里可以设置属性
		initProperties(beanFactory);
    }

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    	registerDataSourceBeans(registry);
        
    }
    
    private void registerDataSourceBeans(BeanDefinitionRegistry registry) {
    	String[] names = datasourceproperties.getNames();
    	if(names==null) {
    		return;
    	}
    	for(int i=0;i<names.length;i++) {
    		String name = names[i];
    		boolean isPrimary = false;
    		String datasourcename = DATASOURCPREFIX+name;
    		String transactionname = TRANSACTIONPREFIX+name;
    		String sqlsessionFactoryname = SQLSESSIONFACTORYPREFIX+name;
    		if(i==0) {
    			isPrimary = true;
    		}
    		//数据源
        	registerBean(registry, datasourcename, DruidDataSource.class,isPrimary);
        	//tansaction
        	registerBean(registry, transactionname, DataSourceTransactionManager.class,isPrimary);
        	//sessionfactory
        	registerBean(registry, sqlsessionFactoryname, SqlSessionFactoryBean.class,isPrimary);
        	//mapperscan
        	registerMapperInfo(registry,sqlsessionFactoryname,datasourceproperties.getProperties().get(name));
    	}
    }

    private void registerMapperInfo(BeanDefinitionRegistry registry,String sqlseesionfactoryname,SingleDataSourceProperties prop) {
    	ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

        scanner.setSqlSessionFactoryBeanName(sqlseesionfactoryname);
        String[] basepackages = prop.getMapperpath().split(",");
        scanner.registerFilters();
        scanner.doScan(basepackages);
	}

	private void registerBean(BeanDefinitionRegistry registry, String name, Class<?> beanClass,boolean isPrimary){
    	AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
        abd.setPrimary(isPrimary);
        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());
        // 可以自动生成name
        String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, registry));
        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    }

    private void initProperties(ConfigurableListableBeanFactory beanFactory) {
    	String[] names = datasourceproperties.getNames();
    	if(names==null) {
    		return ;
    	}
    	for(int i=0;i<names.length;i++) {
    		String name = names[i];
    		SingleDataSourceProperties datasourceprop = datasourceproperties.getProperties().get(name);
    		String datasourcename = DATASOURCPREFIX+name;
    		String transactionname = TRANSACTIONPREFIX+name;
    		String sessionfactoryname = SQLSESSIONFACTORYPREFIX+name;
    		
    		//datasource
    		initDataSource(datasourceprop,datasourcename,(DruidDataSource)beanFactory.getBean(datasourcename));
    		
    		//transaction
    		BeanDefinition transactionbd = beanFactory.getBeanDefinition(transactionname); 
    		MutablePropertyValues transactionbdpv =  transactionbd.getPropertyValues();  
    		transactionbdpv.addPropertyValue("dataSource", beanFactory.getBean(datasourcename));

    		//sqlsessionfactory
    		BeanDefinition bdd = beanFactory.getBeanDefinition(sessionfactoryname);
            MutablePropertyValues mpvd =  bdd.getPropertyValues();  
            mpvd.addPropertyValue("dataSource", beanFactory.getBean(datasourcename));
            try {
    			mpvd.addPropertyValue("mapperLocations", new PathMatchingResourcePatternResolver().getResources(datasourceprop.getMapperxmlpath()));
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
            if(!StringUtils.isEmpty(datasourceprop.getConfiglocation())) {
            	mpvd.addPropertyValue("configLocation", new DefaultResourceLoader().getResource(datasourceprop.getConfiglocation()));
            }
    	}
    }


    /**
     * 初始化数据源
     * @param properties
     * @param datasourcename
     * @param dataSource
     */
    private void initDataSource(SingleDataSourceProperties properties,String datasourcename,DruidDataSource dataSource) {
    	dataSource.setName(datasourcename);
        if (properties.getUrl() != null) {
            dataSource.setUrl(properties.getUrl());
        }
        if (properties.getUsername() != null) {
            dataSource.setUsername(properties.getUsername());
        }
        if (properties.getPassword() != null) {
            dataSource.setPassword(properties.getPassword());
        }
        if (properties.getDriverClassName() != null) {
            dataSource.setDriverClassName(properties.getDriverClassName());
        }
        if (properties.getInitialSize() != null) {
            dataSource.setInitialSize(properties.getInitialSize());
        }
        if (properties.getMaxActive() != null) {
            dataSource.setMaxActive(properties.getMaxActive());
        }
        if (properties.getMinIdle() != null) {
            dataSource.setMinIdle(properties.getMinIdle());
        }
        if (properties.getMaxWait() != null) {
            dataSource.setMaxWait(properties.getMaxWait());
        }
        if (properties.getPoolPreparedStatements() != null) {
            dataSource.setPoolPreparedStatements(properties.getPoolPreparedStatements());
        }
        if (properties.getMaxOpenPreparedStatements() != null) {
            dataSource.setMaxOpenPreparedStatements(properties.getMaxOpenPreparedStatements());
        }
        if (properties.getMaxPoolPreparedStatementPerConnectionSize() != null) {
            dataSource.setMaxPoolPreparedStatementPerConnectionSize(properties.getMaxPoolPreparedStatementPerConnectionSize());
        }
        if (properties.getValidationQuery() != null) {
            dataSource.setValidationQuery(properties.getValidationQuery());
        }
        if (properties.getValidationQueryTimeout() != null) {
            dataSource.setValidationQueryTimeout(properties.getValidationQueryTimeout());
        }
        if (properties.getTestWhileIdle() != null) {
            dataSource.setTestWhileIdle(properties.getTestWhileIdle());
        }
        if (properties.getTestOnBorrow() != null) {
            dataSource.setTestOnBorrow(properties.getTestOnBorrow());
        }
        if (properties.getTestOnReturn() != null) {
            dataSource.setTestOnReturn(properties.getTestOnReturn());
        }
        if (properties.getTimeBetweenEvictionRunsMillis() != null) {
            dataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis());
        }
        if (properties.getMinEvictableIdleTimeMillis() != null) {
            dataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis());
        }
        if (properties.getMaxEvictableIdleTimeMillis() != null) {
            dataSource.setMaxEvictableIdleTimeMillis(properties.getMaxEvictableIdleTimeMillis());
        }
        try {
            if (properties.getFilters() != null) {
                dataSource.setFilters(properties.getFilters());
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
	}

}
